package com.gitee.fastmybatis.spring.boot.autoconfigure.multi;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.util.List;

/**
 * 多数据源配置
 *
 * @author thc
 */
@Configuration(proxyBeanMethods = false)
@AutoConfigureBefore(DataSourceAutoConfiguration.class)
@EnableConfigurationProperties(MultiDataSourceProps.class)
@ConditionalOnMultiDatasource
public class MultiMybatisAutoConfiguration {
    private static final Logger logger = LoggerFactory.getLogger(MultiMybatisAutoConfiguration.class);

    private final ConfigurableListableBeanFactory configurableListableBeanFactory;
    private final MultiDataSourceProps multiDataSourceProps;


    public MultiMybatisAutoConfiguration(
            MultiDataSourceProps multiDataSourceProps,
            ConfigurableListableBeanFactory configurableListableBeanFactory) {
        this.multiDataSourceProps = multiDataSourceProps;
        this.configurableListableBeanFactory = configurableListableBeanFactory;
    }

    /**
     * 主数据源
     */
    @Primary
    @Bean
    public DataSource dataSource() {
        DatasourcePropContext datasourcePropContext = multiDataSourceProps.getMainDataSourceProp();
        MyBatisComponent myBatisComponent = MultiDataSourceContext.get(datasourcePropContext.getDsAlias());
        return myBatisComponent.getDataSource();
    }

    @Primary
    @Bean
    @ConditionalOnMissingBean(DataSourceTransactionManager.class)
    public DataSourceTransactionManager transactionManager(DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        MyBatisComponent myBatisComponent = MultiDataSourceContext.get(dataSource);
        DataSourceTransactionManager transactionManager = myBatisComponent.getTransactionManager();
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
        return transactionManager;
    }

    @Bean
    @Primary
    @ConditionalOnMissingBean(TransactionTemplate.class)
    public TransactionTemplate transactionTemplate(DataSource dataSource) {
        MyBatisComponent myBatisComponent = MultiDataSourceContext.get(dataSource);
        return myBatisComponent.getTransactionTemplate();
    }

    @Primary
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Autowired DataSource dataSource) throws Exception {
        MyBatisComponent myBatisComponent = MultiDataSourceContext.get(dataSource);
        initOtherSqlSessionFactory();
        return myBatisComponent.getSqlSessionFactory();
    }

    @Primary
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Autowired DataSource dataSource) {
        MyBatisComponent myBatisComponent = MultiDataSourceContext.get(dataSource);
        DatasourcePropContext datasourcePropContext = myBatisComponent.getDatasourcePropContext();
        return buildSqlSessionTemplate(datasourcePropContext, myBatisComponent.getSqlSessionFactory());
    }

    private SqlSessionTemplate buildSqlSessionTemplate(DatasourcePropContext datasourcePropContext, SqlSessionFactory sqlSessionFactory) {
        DataSourceProp dataSourceProp = datasourcePropContext.getDataSourceProp();
        ExecutorType executorType = dataSourceProp.getMybatis().getExecutorType();
        if (executorType != null) {
            return new SqlSessionTemplate(sqlSessionFactory, executorType);
        } else {
            return new SqlSessionTemplate(sqlSessionFactory);
        }
    }

    private void initOtherSqlSessionFactory() throws Exception {
        List<DatasourcePropContext> notMainDataSourceProp = multiDataSourceProps.getNotMainDataSourceProp();
        if (notMainDataSourceProp == null || notMainDataSourceProp.isEmpty()) {
            return;
        }
        for (DatasourcePropContext context : notMainDataSourceProp) {
            String dsAlias = context.getDsAlias();
            DataSourceProp dataSourceProp = context.getDataSourceProp();
            String name = dataSourceProp.getName();
            if (name == null || name.isEmpty()) {
                dataSourceProp.setName(dsAlias);
            }
            MyBatisComponent myBatisComponent = MultiDataSourceContext.get(dsAlias);

            registerTransactionManager(context, myBatisComponent);
            registerSqlSessionTemplate(context, myBatisComponent);
        }
    }


    private void registerTransactionManager(DatasourcePropContext context, MyBatisComponent myBatisComponent) {
        PlatformTransactionManager transactionManager = myBatisComponent.getTransactionManager();
        String dsAlias = context.getDsAlias();
        String transactionManagerBeanName = "transactionManager" + dsAlias;
        this.registerBeanDefinition(transactionManagerBeanName, transactionManager);
    }

    private void registerSqlSessionTemplate(DatasourcePropContext datasourcePropContext, MyBatisComponent myBatisComponent) {
        SqlSessionFactory sqlSessionFactory = myBatisComponent.getSqlSessionFactory();
        SqlSessionTemplate sqlSessionTemplate = buildSqlSessionTemplate(datasourcePropContext, sqlSessionFactory);
        String beanName = "sqlSessionTemplate" + datasourcePropContext.getDsAlias();
        registerBeanDefinition(beanName, sqlSessionTemplate);
    }

    private void registerBeanDefinition(String beanName, Object object) {
        this.configurableListableBeanFactory.registerSingleton(beanName, object);
    }

    @org.springframework.context.annotation.Configuration
    @Import({MapperScannerRegistrar.class})
    @ConditionalOnMissingBean(MapperFactoryBean.class)
    @ConditionalOnMultiDatasource
    @EnableConfigurationProperties(MultiDataSourceProps.class)
    public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

        public void afterPropertiesSet() {
            logger.debug("No {} found.", MapperFactoryBean.class.getName());
        }
    }

}
